package com.vk.project.service.impl;

import cn.hutool.core.util.RandomUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.core.toolkit.Wrappers;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.plugins.pagination.PageDTO;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.vk.project.common.DeleteRequest;
import com.vk.project.common.ErrorCode;
import com.vk.project.common.IdListRequest;
import com.vk.project.common.ResultUtils;
import com.vk.project.exception.BusinessException;
import com.vk.project.mapper.UserMapper;
import com.vk.project.model.dto.user.UserAddRequest;
import com.vk.project.model.dto.user.UserQueryRequest;
import com.vk.project.model.dto.user.UserUpdateRequest;
import com.vk.project.model.vo.EmailRegisterVO;
import com.vk.project.model.vo.UserVO;
import com.vk.project.service.UserInterfaceInfoService;
import com.vk.project.service.UserService;
import com.vk.project.untils.Const;
import com.vk.project.untils.FlowUtils;
import com.vk.vkapiclientsdk.client.VkApiClient;
import com.vk.vkapicommon.model.entity.User;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import static com.vk.project.constant.UserConstant.ADMIN_ROLE;
import static com.vk.project.constant.UserConstant.USER_LOGIN_STATE;

/**
 * @ClassName : UserServiceImpl
 * @Description : 用户服务实现类
 * @Author : LYQ
 * @Date: 2023-11-21 13:13:06
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements UserService, UserDetailsService {

    @Autowired
    FlowUtils flowUtils;

    @Autowired
    AmqpTemplate amqpTemplate;

    @Autowired
    UserInterfaceInfoService userInterfaceInfoService;

    @Autowired
    StringRedisTemplate stringRedisTemplate;

    @Autowired
    PasswordEncoder passwordEncoder;

    @Autowired
    private UserMapper userMapper;

    /**
     * 盐值，混淆密码
     */
    private static final String SALT = "MrVK";

    /**
     * 用户注册接口
     *
     * @param username  用户账户
     * @param passwprd  用户密码
     * @param checkPassword 校验密码
     * @return 新用户 id
     */
    @Override
    public long userRegister(String username, String passwprd, String checkPassword) {
        // 校验注册的参数是否为空
        if (StringUtils.isAnyBlank(username, passwprd, checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (username.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户账号过短");
        }
        if (passwprd.length() < 8 || checkPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户密码过短");
        }
        // 密码和校验密码相同
        if (!passwprd.equals(checkPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
        }
        synchronized (username.intern()) {
            // 账户不能重复
            QueryWrapper<User> queryWrapper = new QueryWrapper<>();
            queryWrapper.eq("username", username);
            // 查询是否有重复账户情况
            long count = userMapper.selectCount(queryWrapper);
            if (count > 0) {
                throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号重复");
            }
            // 对用户的密码加密
            String encryptPassword = new BCryptPasswordEncoder().encode(passwprd);
            // 分配用户的 accessKey, secretKey
            // 使用DigestUtils.md5Hex将盐值，用户账户何 5或8 位随机数进行 MD5 加密
            String accessKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(5));
            String secretKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(8));
            // 创建用户数据
            User user = new User();
            user.setUsername(username);
            user.setPassword(encryptPassword);
            user.setAccessKey(accessKey);
            user.setSecretKey(secretKey);
            // 保存用户数据
            boolean saveResult = this.save(user);
            // 保存失败则抛出异常
            if (!saveResult) {
                throw new BusinessException(ErrorCode.SYSTEM_ERROR, "注册失败，数据库错误");
            }
            return user.getId();
        }
    }

    /**
     * 用户登录接口
     *
     * @param userAccount  用户账户
     * @param userPassword 用户密码
     * @param request      请求体
     * @return 脱敏后的用户信息
     */
    @Override
    public User userLogin(String userAccount, String userPassword, HttpServletRequest request) {
        // 校验用户登录的参数是否为空
        if (StringUtils.isAnyBlank(userAccount, userPassword)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "参数为空");
        }
        if (userAccount.length() < 4) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "账号错误");
        }
        if (userPassword.length() < 8) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "密码错误");
        }
        // 加密用户登录时输入的密码
        String encryptPassword = new BCryptPasswordEncoder().encode(userPassword);
        // 查询用户是否存在
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        queryWrapper.eq("userAccount", userAccount);
        queryWrapper.eq("userPassword", encryptPassword);
        User user = userMapper.selectOne(queryWrapper);
        // 如果用户不存在，则抛出异常
        if (user == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "用户不存在或密码错误");
        }
        // 记录用户的登录状态
        request.getSession().setAttribute(USER_LOGIN_STATE, user);
        return user;
    }

    /**
     * 获取当前登录用户信息
     *
     * @param request 当前会话
     * @return 返回用户信息
     */
    @Override
    public User getLoginUser(HttpServletRequest request) {
        // 获取会话
        HttpSession session = request.getSession(false); // 使用false作为参数表示如果当前没有会话则不创建新会话

        // 打印会话ID
        if (session != null) {
            String sessionId = session.getId();
            System.out.println("Session ID: " + sessionId); // 或者使用日志框架记录，如 log.info("Session ID: {}", sessionId);
        } else {
            System.out.println("No session found.");
        }

        // 获取用户的登录状态，判断是否已登录
        Object userObj = session.getAttribute(USER_LOGIN_STATE);
        if (userObj != null) {
            User currentUser = (User) userObj;

            // 返回用户信息的响应对象
            return currentUser;
        } else {
            // 用户未登录或没有找到用户信息
            return null;
        }
    }

    /**
     * 判断是否为管理员
     *
     * @param request 请求体
     * @return 返回判断结果
     */
    @Override
    public boolean isAdmin(HttpServletRequest request) {
        // 获取用户的登录状态，判断是否已登录
        Object userObj = request.getSession().getAttribute(USER_LOGIN_STATE);
        User user = (User) userObj;
        // 返回判断结果
        return user != null && ADMIN_ROLE.equals(user.getUserRole());
    }

    /**
     * 用户注销接口
     *
     * @param request 请求体
     * @return 返回注销结果
     */
    @Override
    public boolean userLogout(HttpServletRequest request) {
        // 如果获取不到用户登录的信息
        if (request.getSession().getAttribute(USER_LOGIN_STATE) == null) {
            throw new BusinessException(ErrorCode.OPERATION_ERROR, "未登录");
        }
        // 移除该用户的登录态
        request.getSession().removeAttribute(USER_LOGIN_STATE);
        return true;
    }

    /**
     * 创建用户
     *
     * @param userAddRequest 用户创建请求对象
     * @param request        请求体
     * @return
     */
    @Override
    public String addUser(UserAddRequest userAddRequest, HttpServletRequest request) {
        String username = userAddRequest.getUsername();
        String password = userAddRequest.getPassword();
        if(this.existsAccountByEmail(userAddRequest.getUsermail())) {
            return "该邮件地址已被注册";
        }
        if(this.existsAccountByUsername(userAddRequest.getUsername())) {
            return "该用户名已被他人使用，请重新更换";
        }
        if (username.length() < 2 && username.length() > 8) {
            return "用户名的长度必须在2-8个字符之间";
        }
        if (password.length() < 6 || password.length() > 16) {
            return "密码的长度必须在6-16个字符之间";
        }

        // 将用户密码进行加密
        String passwordEncryption = passwordEncoder.encode(password);
        // 使用DigestUtils.md5Hex将盐值，用户账户何 5或8 位随机数进行 MD5 加密
        String accessKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(5));
        String secretKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(8));

        User user = new User();
        user.setUsername(userAddRequest.getUsername());
        user.setPassword(passwordEncryption);
        user.setUsermail(userAddRequest.getUsermail());
        user.setUserRole(Const.ROLE_DEFAULT);
        user.setAccessKey(accessKey);
        user.setSecretKey(secretKey);

        if(!this.save(user)) {
            return "内部错误，注册失败";
        } else {
            return null;
        }
    }

    /**
     * 更新用户
     *
     * @param userUpdateRequest 用户更新请求对象
     * @param request           请求体
     * @return
     */
    @Override
    public boolean updateUser(UserUpdateRequest userUpdateRequest, HttpServletRequest request) {
        // 如果用户更新请求为空或者用户ID为空，抛出参数错误异常
        if (userUpdateRequest == null || userUpdateRequest.getId() == null) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        // 创建一个User对象
        User user = new User();
        // 将用户更新请求中的数据复制到User对象中
        BeanUtils.copyProperties(userUpdateRequest, user);
        // 调用用户服务中的updateById方法，更新用户信息
        boolean result = this.updateById(user);
        return result;
    }

    /**
     * 根据用户Id删除对应的用户
     *
     * @param idListRequest 删除请求
     * @param request       当前会话
     * @return 返回删除结果
     */
    @Override
    public boolean deleteUserById(IdListRequest idListRequest, HttpServletRequest request) {
        if (idListRequest == null || idListRequest.getIdList().size() <= 0) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR);
        }
        for (Long userId : idListRequest.getIdList()) {
            // 删除用户信息
            boolean deleteUser = this.removeById(userId);
            // 删除用户开通接口的信息
            boolean deleteUserInterfaceInfo = userInterfaceInfoService.removeUserInterfaceInfoByUid(userId);
        }

        return true;
    }

    /**
     * 创建SDK客户端
     *
     * @param user 当前登录用户
     * @return 返回SDK客户端
     */
    @Override
    public VkApiClient getApiClient(HttpServletRequest request) {
        // 获取当前登录的用户
        User loginUser = getLoginUser(request);
        // 获取密钥信息
        String accessKey = loginUser.getAccessKey();
        String secretKey = loginUser.getSecretKey();
        // 创建SDK客户端
        VkApiClient client = new VkApiClient(accessKey, secretKey);
        return client;
    }

    @Override
    public User findUserByNameOrEmail(String text) {
        return this.query()
                .eq("username", text)
                .one();
    }

    /**
     * 分页查询用户信息
     *
     * @param userQueryRequest 查询条件
     * @param request          当前会话
     * @return 返回用户信息列表
     */
    @Override
    public Page<UserVO> listUserByPage(UserQueryRequest userQueryRequest, HttpServletRequest request) {
        // 当前页数
        long current = userQueryRequest.getCurrent();
        // 每页显示的数量
        long size = userQueryRequest.getPageSize();

        Page<User> page = new Page<>(current, size);
        IPage<UserVO> pageResult = userMapper.findByCondition(page, userQueryRequest);

        Page<UserVO> userVOPage = new PageDTO<>(page.getCurrent(), page.getSize(), page.getTotal());
        // 将UserVO列表放入分页信息中
        userVOPage.setRecords(pageResult.getRecords());
        return userVOPage;
    }

    /**
     * 发送邮箱验证码
     *
     * @param type  发送场景（注册，找回密码）
     * @param email 邮箱号
     * @param ip    用户IP地址
     * @return 邮箱验证码
     */
    @Override
    public String registerEmailVerifyCode(String type, String email, String ip) {
        synchronized (ip.intern()) {
            if (!verifyLimit(ip)) {
                return "请求频繁，请稍后再试";
            }
            Random random = new Random();
            int code = random.nextInt(899999) + 100000;
            Map<String, Serializable> data = new HashMap<>();
            data.put("type", type);
            data.put("email", email);
            data.put("code", code);
            data.put("ip", ip);
            amqpTemplate.convertAndSend("mail", data);
            stringRedisTemplate.opsForValue()
                    .set(Const.VERIFY_EMAIL_DATA + email, String.valueOf(code), 3, TimeUnit.MINUTES);
            return null;
        }
    }

    /**
     * 注册邮箱账号
     *
     * @param info 用户注册表单信息对象
     * @return 操作结果，null表示正常，否则为错误原因
     */
    @Override
    public String registerEmailAccount(EmailRegisterVO info) {
        String email = info.getEmail();
        String code = this.getEmailVerifyCode(email);
        if(code == null) {
            return "请先获取验证码";
        }
        if(!code.equals(info.getCode())) {
            return "验证码错误，请重新输入";
        }
        if(this.existsAccountByEmail(email)) {
            return "该邮件地址已被注册";
        }

        String username = info.getUsername();
        String password = info.getPassword();
        String passwordRepeat = info.getPasswordRepeat();

        if(this.existsAccountByUsername(username)) {
            return "该用户名已被他人使用，请重新更换";
        }
        if (username.length() < 2 && username.length() > 8) {
            return "用户名的长度必须在2-8个字符之间";
        }
        if (password.length() < 6 || passwordRepeat.length() > 16) {
            return "密码的长度必须在6-16个字符之间";
        }
        // 密码和校验密码相同
        if (!password.equals(passwordRepeat)) {
            throw new BusinessException(ErrorCode.PARAMS_ERROR, "两次输入的密码不一致");
        }

        // 将用户密码进行加密
        String passwordEncryption = passwordEncoder.encode(password);
        // 使用DigestUtils.md5Hex将盐值，用户账户何 5或8 位随机数进行 MD5 加密
        String accessKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(5));
        String secretKey = org.apache.commons.codec.digest.DigestUtils.md5Hex(SALT + username + RandomUtil.randomNumbers(8));

        User user = new User();
        user.setUsername(info.getUsername());
        user.setPassword(passwordEncryption);
        user.setUsermail(email);
        user.setUserRole(Const.ROLE_DEFAULT);
        user.setAccessKey(accessKey);
        user.setSecretKey(secretKey);

        if(!this.save(user)) {
            return "内部错误，注册失败";
        } else {
            this.deleteEmailVerifyCode(email);
            return null;
        }
    }

    @Override
    public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
        User account = this.findUserByNameOrEmail(username);
        if (account == null)
            throw new UsernameNotFoundException("用户名或密码错误");
        return org.springframework.security.core.userdetails.User
                .withUsername(username)
                .password(account.getPassword())
                .roles(account.getUserRole())
                .build();
    }

    /**
     * 获取Redis中存储的邮件验证码
     * @param email 电邮
     * @return 验证码
     */
    private String getEmailVerifyCode(String email){
        String key = Const.VERIFY_EMAIL_DATA + email;
        return stringRedisTemplate.opsForValue().get(key);
    }

    /**
     * 移除Redis中存储的邮件验证码
     * @param email 电邮
     */
    private void deleteEmailVerifyCode(String email){
        String key = Const.VERIFY_EMAIL_DATA + email;
        stringRedisTemplate.delete(key);
    }

    /**
     * 针对IP地址进行邮件验证码获取限流
     * @param address 地址
     * @return 是否通过验证
     */
    private boolean verifyLimit(String ip) {
        String key = Const.VERIFY_EMAIL_LIMIT + ip;
        return flowUtils.limitOnceCheck(key, 60);
    }

    /**
     * 查询指定邮箱的用户是否已经存在
     * @param email 邮箱
     * @return 是否存在
     */
    private boolean existsAccountByEmail(String email){
        return this.baseMapper.exists(Wrappers.<User>query().eq("usermail", email));
    }

    /**
     * 查询指定用户名的用户是否已经存在
     * @param username 用户名
     * @return 是否存在
     */
    private boolean existsAccountByUsername(String username){
        return this.baseMapper.exists(Wrappers.<User>query().eq("username", username));
    }
}




